home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Amiga Collections: Camelot
/
Camelot 078 (1990-06)(Swedish User Group of Amiga)(SE)(PD)[WB].zip
/
Camelot 078 (1990-06)(Swedish User Group of Amiga)(SE)(PD)[WB].adf
/
MSH
/
src
/
hansec.c
< prev
next >
Wrap
C/C++ Source or Header
|
1990-06-17
|
21KB
|
938 lines
/*-
* $Id: hansec.c,v 1.30 90/06/04 23:17:02 Rhialto Rel $
* $Log: hansec.c,v $
* Revision 1.30 90/06/04 23:17:02 Rhialto
* Release 1 Patch 3
*
* HANSEC.C
*
* The code for the messydos file system handler.
*
* Sector-level stuff: read, write, cache, unit conversion.
* Other interactions (via MyDoIO) with messydisk.device.
*
* This code is (C) Copyright 1989,1990 by Olaf Seibert. All rights reserved.
* May not be used or copied without a licence.
-*/
#include "dos.h"
#include "han.h"
/*#undef HDEBUG /**/
#ifdef HDEBUG
# define debug(x) dbprintf x
#else
# define debug(x)
#endif
struct MsgPort *DiskReplyPort;
struct IOExtTD *DiskIOReq;
struct IOStdReq *DiskChangeReq;
struct DiskParam Disk;
byte *Fat;
short FatDirty; /* Fat must be written to disk */
short error; /* To put the error value; for Result2 */
long IDDiskState; /* InfoData.id_DiskState */
long IDDiskType; /* InfoData.id_DiskType */
struct timerequest *TimeIOReq; /* For motor-off delay */
struct MinList CacheList; /* Sector cache */
int CurrentCache; /* How many cached buffers do we have */
int MaxCache; /* Maximum amount of cached buffers */
long CacheBlockSize; /* Size of disk block + overhead */
ulong BufMemType;
int DelayState;
short CheckBootBlock; /* Do we need to check the bootblock? */
word
Get8086Word(Word8086)
register byte *Word8086;
{
return Word8086[0] | Word8086[1] << 8;
}
word
OtherEndianWord(oew)
word oew;
{
/* INDENT OFF */
#asm
move.w 8(a5),d0
rol.w #8,d0
#endasm
/* INDENT ON */
/*
* return (oew << 8) | ((oew >> 8) & 0xff);
*/
}
ulong
OtherEndianLong(oel)
ulong oel;
{
/* INDENT OFF */
#asm
move.l 8(a5),d0
rol.w #8,d0
swap d0
rol.w #8,d0
#endasm
/* INDENT ON */
/*
* return ((oel & 0xff) << 24) | ((oel & 0xff00) << 8) |
* ((oel & 0xff0000) >> 8) | ((oel & 0xff000000) >> 24);
*/
}
void
OtherEndianMsd(msd)
register struct MsDirEntry *msd;
{
msd->msd_Date = OtherEndianWord(msd->msd_Date);
msd->msd_Time = OtherEndianWord(msd->msd_Time);
msd->msd_Cluster = OtherEndianWord(msd->msd_Cluster);
msd->msd_Filesize = OtherEndianLong(msd->msd_Filesize);
}
word
ClusterToSector(cluster)
register word cluster;
{
return cluster ? Disk.start + cluster * Disk.spc
: 0;
}
word
ClusterOffsetToSector(cluster, offset)
register word cluster;
register word offset;
{
return cluster ? Disk.start + cluster * Disk.spc + offset / Disk.bps
: 0;
}
word
DirClusterToSector(cluster)
register word cluster;
{
return cluster ? Disk.start + cluster * Disk.spc
: Disk.rootdir;
}
word
SectorToCluster(sector)
register word sector;
{
return sector ? (sector - Disk.start) / Disk.spc
: 0;
}
/*
* Get the next cluster in a chain. Sort-of checks for special entries.
*/
word
NextCluster(cluster)
word cluster;
{
register word entry;
return (entry = GetFatEntry(cluster)) >= 0xFFF7 ? FAT_EOF : entry;
}
word
NextClusteredSector(sector)
word sector;
{
word next = (sector + 1 - Disk.start) % Disk.spc;
if (next == 0) {
next = NextCluster(SectorToCluster(sector));
return next != FAT_EOF ? ClusterToSector(next)
: SEC_EOF;
} else
return sector + 1;
}
#ifndef READONLY
word
FindFreeSector(prev)
word prev;
{
word freecluster = FindFreeCluster(SectorToCluster(prev));
return freecluster == FAT_EOF ? SEC_EOF : ClusterToSector(freecluster);
}
#endif
/*
* Find a specific sector. The cache list is a Least Recently Used stack:
* Put it on the head of the cache list. So if it is not used anymore in a
* long time, it bubbles to the end of the list, getting a higher chance
* of being trashed for re-use.
*/
struct CacheSec *
FindSecByNumber(number)
register int number;
{
register struct CacheSec *sec;
register struct CacheSec *nextsec;
debug(("FindSecByNumber %d", number));
for (sec = (void *) CacheList.mlh_Head;
nextsec = (void *) sec->sec_Node.mln_Succ; sec = nextsec) {
if (sec->sec_Number == number) {
debug((" (%x) %lx\n", sec->sec_Refcount, sec));
Remove(sec);
AddHead(&CacheList, &sec->sec_Node);
return sec;
}
}
debug(("; "));
return NULL;
}
struct CacheSec *
FindSecByBuffer(buffer)
byte *buffer;
{
return (struct CacheSec *) (buffer - OFFSETOF(CacheSec, sec_Data));
}
/*
* Get a fresh cache buffer. If we are allowed more cache, we just
* allocate memory. Otherwise, we try to find a currently unused buffer.
* We start looking at the end of the list, which is the bottom of the LRU
* stack. If that fails, allocate more memory anyway. Not that is likely
* anyway, since we currently lock only one sector at a time.
*/
struct CacheSec *
NewCacheSector()
{
register struct CacheSec *sec;
register struct CacheSec *nextsec;
debug(("NewCacheSector\n"));
if (CurrentCache < MaxCache) {
if (sec = AllocMem(CacheBlockSize, BufMemType)) {
goto add;
}
}
for (sec = (void *) CacheList.mlh_TailPred;
nextsec = (void *) sec->sec_Node.mln_Pred; sec = nextsec) {
if ((CurrentCache >= MaxCache) && (sec->sec_Refcount == SEC_DIRTY)) {
FreeCacheSector(sec); /* Also writes it to disk */
continue;
}
if (sec->sec_Refcount == 0) /* Implies not SEC_DIRTY */
return sec;
}
sec = AllocMem(CacheBlockSize, BufMemType);
if (sec) {
add:
CurrentCache++;
AddHead(&CacheList, &sec->sec_Node);
} else
error = ERROR_NO_FREE_STORE;
return sec;
}
/*
* Dispose a cached sector, even if it has a non-zero refcount. If it is
* dirty, write it out.
*/
void
FreeCacheSector(sec)
register struct CacheSec *sec;
{
debug(("FreeCacheSector %d\n", sec->sec_Number));
Remove(sec);
#ifndef READONLY
if (sec->sec_Refcount & SEC_DIRTY) {
PutSec(sec->sec_Number, sec->sec_Data);
}
#endif
FreeMem(sec, CacheBlockSize);
CurrentCache--;
}
/*
* Create an empty cache list
*/
void
InitCacheList()
{
extern struct CacheSec *sec; /* Of course this does not exist... */
NewList(&CacheList);
CurrentCache = 0;
CacheBlockSize = Disk.bps + sizeof (*sec) - sizeof (sec->sec_Data);
}
/*
* Dispose all cached sectors, possibly writing them to disk.
*/
void
FreeCacheList()
{
register struct CacheSec *sec;
debug(("FreeCacheList, %d\n", CurrentCache));
while (sec = GetHead(&CacheList)) {
FreeCacheSector(sec);
}
}
/*
* Do an insertion sort on tosort in the CacheList. Since it changes the
* location in the list, you must fetch it before calling this routine.
* The list will become ascending.
*/
void
SortSec(tosort)
register struct CacheSec *tosort;
{
register struct CacheSec *sec;
struct CacheSec *nextsec;
register word secno;
secno = tosort->sec_Number;
debug(("SortSec %d: ", secno));
for (sec = (void *) CacheList.mlh_Head;
nextsec = (void *) sec->sec_Node.mln_Succ; sec = nextsec) {
debug(("%d, ", sec->sec_Number));
if (sec == tosort) {
debug(("\n"));
return; /* No need to move it away */
}
if (sec->sec_Number > secno)
break;
}
/* Insert before sec */
Remove(tosort);
Insert(&CacheList, tosort, sec->sec_Node.mln_Pred);
debug(("\n"));
}
/*
* Write all dirty cache buffers to disk. They are written from highest to
* lowest, and then the FAT is written out.
*/
void
MSUpdate(immediate)
int immediate;
{
register struct CacheSec *sec;
register struct CacheSec *nextsec;
debug(("MSUpdate\n"));
#ifndef READONLY
if (DelayState & DELAY_DIRTY) {
/*
* First sort all dirty sectors on block number
*/
for (sec = (void *) CacheList.mlh_Head;
nextsec = (void *) sec->sec_Node.mln_Succ; sec = nextsec) {
if (sec->sec_Refcount & SEC_DIRTY) {
SortSec(sec);
}
}
/*
* Then do a second (backward) scan to write them out.
*/
for (sec = (void *) CacheList.mlh_TailPred;
nextsec = (void *) sec->sec_Node.mln_Pred; sec = nextsec) {
if (sec->sec_Refcount & SEC_DIRTY) {
PutSec(sec->sec_Number, sec->sec_Data);
sec->sec_Refcount &= ~SEC_DIRTY;
}
}
DelayState &= ~DELAY_DIRTY;
}
if (FatDirty) {
WriteFat();
}
#endif
if (immediate)
DelayState = DELAY_RUNNING1;
if (DelayState & DELAY_RUNNING2) {
StartTimer();
DelayState &= ~DELAY_RUNNING2;
} else { /* DELAY_RUNNING1 */
#ifndef READONLY
while (TDUpdate() != 0 && RetryRwError(DiskIOReq))
;
#endif
TDMotorOff();
DelayState = DELAY_OFF;
}
}
/*
* Start the timer which triggers cache writing and stopping the disk
* motor.
*/
void
StartTimer()
{
DelayState |= DELAY_RUNNING1 | DELAY_RUNNING2;
if (CheckIO(TimeIOReq)) {
WaitIO(TimeIOReq);
TimeIOReq->tr_node.io_Command = TR_ADDREQUEST;
TimeIOReq->tr_time.tv_secs = 3;
TimeIOReq->tr_time.tv_micro = 0;
SendIO(TimeIOReq);
}
}
/*
* Get a pointer to a logical sector { 0, ..., MS_SECTCNT - 1}. We
* allocate a buffer and copy the data in, and lock the buffer until
* FreeSec() is called.
*/
byte *
GetSec(sector)
int sector;
{
struct CacheSec *sec;
#ifdef HDEBUG
if (sector == 0) {
debug(("************ GetSec(0) ***************\n"));
}
#endif
if (sec = FindSecByNumber(sector)) {
sec->sec_Refcount++;
return sec->sec_Data;
}
if (sec = NewCacheSector()) {
register struct IOExtTD *req;
sec->sec_Number = sector;
sec->sec_Refcount = 1;
debug(("GetSec %d\n", sector));
req = DiskIOReq;
do {
req->iotd_Req.io_Command = ETD_READ;
req->iotd_Req.io_Data = (APTR)sec->sec_Data;
req->iotd_Req.io_Offset = Disk.lowcyl + (long) sector * Disk.bps;
req->iotd_Req.io_Length = Disk.bps;
MyDoIO(req);
} while (req->iotd_Req.io_Error != 0 && RetryRwError(req));
StartTimer();
if (req->iotd_Req.io_Error == 0) {
return sec->sec_Data;
}
error = ERROR_NOT_A_DOS_DISK;
FreeCacheSector(sec);
}
return NULL;
}
#ifndef READONLY
byte *
EmptySec(sector)
int sector;
{
byte *buffer;
register struct CacheSec *sec;
#ifdef HDEBUG
if (sector == 0) {
debug(("************ EmptySec(0) ***************\n"));
}
#endif
if (sec = FindSecByNumber(sector)) {
sec->sec_Refcount++;
return sec->sec_Data;
}
if (sec = NewCacheSector()) {
sec->sec_Number = sector;
sec->sec_Refcount = 1;
return sec->sec_Data;
}
return NULL;
}
void
PutSec(sector, data)
int sector;
byte *data;
{
register struct IOExtTD *req;
debug(("PutSec %d\n", sector));
req = DiskIOReq;
do {
req->iotd_Req.io_Command = ETD_WRITE;
req->iotd_Req.io_Data = (APTR) data;
req->iotd_Req.io_Offset = Disk.lowcyl + (long) sector * Disk.bps;
req->iotd_Req.io_Length = Disk.bps;
MyDoIO(req);
} while (req->iotd_Req.io_Error != 0 && RetryRwError(req));
StartTimer();
}
#endif
/*
* Unlock a cached sector. When the usage count drops to zero, which
* implies it is not dirty, and we are over our cache quota, the sector is
* freed. Otherwise we keep it for re-use.
*/
void
FreeSec(buffer)
byte *buffer;
{
register struct CacheSec *sec;
if (sec = FindSecByBuffer(buffer)) {
#ifdef HDEBUG
if (sec->sec_Number == 0) {
debug(("************ FreeSec(0) ***************\n"));
}
#endif
if (--sec->sec_Refcount == 0) { /* Implies not SEC_DIRTY */
if (CurrentCache > MaxCache) {
FreeCacheSector(sec);
}
}
}
}
#ifndef READONLY
void
MarkSecDirty(buffer)
byte *buffer;
{
register struct CacheSec *sec;
if (sec = FindSecByBuffer(buffer)) {
sec->sec_Refcount |= SEC_DIRTY;
DelayState |= DELAY_DIRTY;
StartTimer();
}
}
/*
* Write out the FAT. Called from MSUpdate(), so don't call it again from
* here. Don't use precious cache space for it; you could say it has its
* own private cache already.
*/
void
WriteFat()
{
register int fat,
sec;
int disksec = Disk.res; /* First FAT, first sector */
/* Write all FATs */
for (fat = 0; fat < Disk.nfats; fat++) {
for (sec = 0; sec < Disk.spf; sec++) {
PutSec(disksec++, Fat + sec * Disk.bps);
/* return; /* Fat STILL dirty! */
}
}
FatDirty = FALSE;
}
#endif
int
AwaitDFx()
{
debug(("AwaitDFx\n"));
if (DosType) {
static char dfx[] = "DFx:";
void *dfxProc,
*DeviceProc();
char xinfodata[sizeof(struct InfoData) + 3];
struct InfoData *infoData;
int triesleft;
dfx[2] = '0' + UnitNr;
infoData = (struct InfoData *)(((long)&xinfodata[3]) & ~3L);
for (triesleft = 10; triesleft; triesleft--) {
debug(("AwaitDFx %d\n", triesleft));
if ((dfxProc = DeviceProc(dfx)) == NULL)
break;
dos_packet(dfxProc, ACTION_DISK_INFO, CTOB(infoData));
debug(("AwaitDFx %lx\n", infoData->id_DiskType));
if (infoData->id_DiskType == ID_NO_DISK_PRESENT) {
/* DFx has not noticed yet. Wait a bit. */
WaitIO(TimeIOReq);
TimeIOReq->tr_node.io_Command = TR_ADDREQUEST;
TimeIOReq->tr_time.tv_secs = 0;
TimeIOReq->tr_time.tv_micro = 750000L; /* .75 s */
SendIO(TimeIOReq);
continue;
}
if (infoData->id_DiskType == ID_DOS_DISK) {
/* DFx: understands it, so it is not for us. */
return 1;
}
/*
* All (well, most) other values mean that DFx: does not
* understand it, so we can give it a try.
*/
break;
}
}
return 0;
}
int
ReadBootBlock()
{
int protstatus;
debug(("ReadBootBlock\n"));
FreeFat(); /* before disk parameters change */
TDClear();
if (TDProtStatus() >= 0) {
register byte *bootblock;
short oldCancel;
oldCancel = Cancel;
if (AwaitDFx())
goto bad_disk;
if ((protstatus = TDProtStatus()) < 0)
goto no_disk;
TDChangeNum();
debug(("Changenumber = %ld\n", DiskIOReq->iotd_Count));
Cancel = 1;
if (bootblock = GetSec(0)) {
word bps;
if (CheckBootBlock &&
/* Atari: empty or 68000 JMP */
/*bootblock[0] != 0x00 && bootblock[0] != 0x4E &&*/
/* 8086 ml for a jump */
bootblock[0] != 0xE9 && bootblock[0] != 0xEB) {
goto bad_disk;
}
bps = Get8086Word(bootblock + 0x0b);
Disk.spc = bootblock[0x0d];
Disk.res = Get8086Word(bootblock + 0x0e);
Disk.nfats = bootblock[0x10];
Disk.ndirs = Get8086Word(bootblock + 0x11);
Disk.nsects = Get8086Word(bootblock + 0x13);
Disk.media = bootblock[0x15];
Disk.spf = Get8086Word(bootblock + 0x16);
Disk.spt = Get8086Word(bootblock + 0x18);
Disk.nsides = Get8086Word(bootblock + 0x1a);
Disk.nhid = Get8086Word(bootblock + 0x1c);
FreeSec(bootblock);
/*
* Maybe the sector size just changed. Who knows?
*/
if (Disk.bps != bps) {
FreeCacheList();
Disk.bps = bps;
InitCacheList();
}
Disk.ndirsects = (Disk.ndirs * MS_DIRENTSIZE) / Disk.bps;
Disk.rootdir = Disk.res + Disk.spf * Disk.nfats;
Disk.datablock = Disk.rootdir + Disk.ndirsects;
Disk.start = Disk.datablock - MS_FIRSTCLUST * Disk.spc;
/* Available clusters are 2..maxclust in secs start..nsects-1 */
Disk.maxclst = (Disk.nsects - Disk.start) / Disk.spc - 1;
Disk.bpc = Disk.bps * Disk.spc;
Disk.vollabel = FakeRootDirEntry;
/* Disk.fat16bits = Disk.nsects > 20740; /* DOS3.2 magic value */
Disk.fat16bits = Disk.maxclst >= 0xFF7; /* DOS3.0 magic value */
debug(("%x\tbytes per sector\n", Disk.bps));
debug(("%x\tsectors per cluster\n", Disk.spc));
debug(("%x\treserved blocks\n", Disk.res));
debug(("%x\tfats\n", Disk.nfats));
debug(("%x\tdirectory entries\n", Disk.ndirs));
debug(("%x\tsectors\n", Disk.nsects));
debug(("%x\tmedia byte\n", Disk.media));
debug(("%x\tsectors per FAT\n", Disk.spf));
debug(("%x\tsectors per track\n", Disk.spt));
debug(("%x\tsides\n", Disk.nsides));
debug(("%x\thidden sectors\n", Disk.nhid));
debug(("%x\tdirectory sectors\n", Disk.ndirsects));
debug(("%x\troot dir block\n", Disk.rootdir));
debug(("%x\tblock for (imaginary) cluster 0\n", Disk.start));
debug(("%x\tfirst data block\n", Disk.datablock));
debug(("%x\tclusters total\n", Disk.maxclst));
debug(("%x\tbytes per cluster\n", Disk.bpc));
debug(("%x\t16-bits FAT?\n", Disk.fat16bits));
IDDiskType = ID_DOS_DISK;
#ifdef READONLY
IDDiskState = ID_WRITE_PROTECTED;
#else
if (protstatus > 0)
IDDiskState = ID_WRITE_PROTECTED;
else
IDDiskState = ID_VALIDATED;
#endif
if (Disk.nsects / (MS_SPT * Disk.nsides) <= 40)
DiskIOReq->iotd_Req.io_Flags |= IOMDF_40TRACKS;
else
DiskIOReq->iotd_Req.io_Flags &= ~IOMDF_40TRACKS;
GetFat();
} else {
debug(("Can't read %d.\n", DiskIOReq->iotd_Req.io_Error));
bad_disk:
FreeCacheList();
error = ERROR_NO_DISK;
IDDiskType = ID_UNREADABLE_DISK;
IDDiskState = ID_WRITE_PROTECTED;
}
Cancel = oldCancel;
}
#ifdef HDEBUG
else debug(("No disk inserted %d.\n", DiskIOReq->iotd_Req.io_Error));
#endif
no_disk:
return 1;
}
/*
* We try to identify the disk currently in the drive, trying to find the
* volume label in the first directory block.
*/
int
IdentifyDisk(name, date)
char *name; /* Should be at least 32 characters */
struct DateStamp *date;
{
debug(("IdentifyDisk\n"));
ReadBootBlock(); /* Also sets default vollabel */
if (IDDiskType == ID_DOS_DISK) {
byte *dirblock;
register struct MsDirEntry *dirent;
if (dirblock = GetSec(Disk.rootdir)) {
dirent = (struct MsDirEntry *) dirblock;
while ((byte *) dirent < &dirblock[Disk.bps]) {
if (dirent->msd_Attributes & ATTR_VOLUMELABEL) {
Disk.vollabel.de_Msd = *dirent;
Disk.vollabel.de_Sector = Disk.rootdir;
Disk.vollabel.de_Offset = (byte *) dirent - dirblock;
OtherEndianMsd(&Disk.vollabel.de_Msd);
Disk.vollabel.de_Msd.msd_Cluster = 0; /* to be sure */
break;
}
dirent++;
}
strncpy(name, Disk.vollabel.de_Msd.msd_Name, 8 + 3);
name[8 + 3] = '\0';
ZapSpaces(name, name + 8 + 3);
ToDateStamp(date, Disk.vollabel.de_Msd.msd_Date,
Disk.vollabel.de_Msd.msd_Time);
debug(("Disk is called '%s'\n", name));
FreeSec(dirblock);
return 0;
}
}
return 1;
}
/*
* Remove the disk change SoftInt. The V1.2 / V1.3 version is broken, so
* we use a workaround. The correct thing to do is shown but not used.
*/
void
TDRemChangeInt()
{
if (DiskChangeReq) {
register struct IOExtTD *req = DiskIOReq;
#if 0 /* V1.2 and V1.3 have a broken
* TD_REMCHANGEINT */
req->iotd_Req.io_Command = TD_REMCHANGEINT;
req->iotd_Req.io_Data = (void *) DiskChangeReq;
MyDoIO(req);
WaitIO(DiskChangeReq);
#else
Forbid();
Remove(DiskChangeReq);
Permit();
#endif
DeleteExtIO(DiskChangeReq);
DiskChangeReq = NULL;
}
}
/*
* Set the disk change SoftInt. Return nonzero on failure.
*/
int
TDAddChangeInt(interrupt)
struct Interrupt *interrupt;
{
register struct IOExtTD *req = DiskIOReq;
if (DiskChangeReq) {
TDRemChangeInt();
}
DiskChangeReq = (void *)CreateExtIO(DiskReplyPort,
(long) sizeof (*DiskChangeReq));
if (DiskChangeReq) {
/* Clone IO request part */
DiskChangeReq->io_Device = req->iotd_Req.io_Device;
DiskChangeReq->io_Unit = req->iotd_Req.io_Unit;
DiskChangeReq->io_Command = TD_ADDCHANGEINT;
DiskChangeReq->io_Data = (void *) interrupt;
SendIO(DiskChangeReq);
return 0;
}
return 1;
}
/*
* Get the current disk change number. Necessary for ETD_ commands. Makes
* absolutely sure nobody can change the disk without us noticing it.
*/
int
TDChangeNum()
{
register struct IOExtTD *req = DiskIOReq;
req->iotd_Req.io_Command = TD_CHANGENUM;
MyDoIO(req);
req->iotd_Count = req->iotd_Req.io_Actual;
return req->iotd_Req.io_Actual;
}
/*
* Get the current write protection state.
*
* Zero means writable, one means write protected, minus one means
* no disk in drive.
*/
int
TDProtStatus()
{
register struct IOExtTD *req = DiskIOReq;
req->iotd_Req.io_Command = TD_PROTSTATUS;
MyDoIO(req);
if (req->iotd_Req.io_Error)
return -1;
return req->iotd_Req.io_Actual != 0;
}
/*
* Switch the drive motor off. Return previous state. Don't use this when
* you have allocated the disk via GetDrive().
*/
int
TDMotorOff()
{
register struct IOExtTD *req = DiskIOReq;
req->iotd_Req.io_Command = TD_MOTOR;
req->iotd_Req.io_Length = 0;
MyDoIO(req);
return req->iotd_Req.io_Actual;
}
/*
* Clear all internal messydisk buffers.
*/
int
TDClear()
{
register struct IOExtTD *req = DiskIOReq;
req->iotd_Req.io_Command = CMD_CLEAR;
return MyDoIO(req);
}
#ifndef READONLY
/*
* Write out all internal messydisk buffers to the disk.
*/
int
TDUpdate()
{
register struct IOExtTD *req = DiskIOReq;
req->iotd_Req.io_Command = ETD_UPDATE;
return MyDoIO(req);
}
#endif
int
MyDoIO(ioreq)
register struct IOStdReq *ioreq;
{
ioreq->io_Flags |= IOF_QUICK; /* Preserve IOMDF_40TRACKS */
BeginIO(ioreq);
return WaitIO(ioreq);
}